I have a layout component that needs onAppBarInputChange prop. The onAppBarInputChange prop expected a function that take the input value from the layout component, and filter the todos based on that input value.
How do I pass the props from the todos page to the layout component?
todos.jsx
import {useState} from 'react'
import Layout from './layout'
const Todos = () => {
const [query, setQuery] = useState('')
const todos = [
{
id: 0,
text: 'make some projects'
},
{
id: 1,
text: 'fix some bugs'
},
{
id: 2,
text: 'cook food at home'
}
]
const searchedTodos = todos.filter(todo => todo.toLowerCase().includes(query.toLowerCase()))
return (
<ul>
{searchedTodos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
)
}
Todos.getLayout = function getLayout(page) {
return (
{/* how to set the query like this? */}
<Layout onAppBarInputChage={() => setQuery(e.targe.value)}>
{page}
</Layout>
)
}
export default Todos;
layout.jsx
const Layout = ({children, onAppBarInputChange}) => {
return (
<div>
<header>
<div>Todo page</div>
<input onChange={onAppBarInputChange} />
</header>
<main>{children}</main>
<footer>
some footer here
</footer>
</div>
)
}
export default Layout
Note: I had read the documentation from the next.js website, about how to add layout in next.js, however they don't show any examples on how to pass the props to the layout component
How about passing the input value through Context?
By adopting Context every component can observe the input value easily.
context/app.jsx
const AppContext = createContext(null);
const AppContextProvider = ({ children }) => {
const [query, setQuery] = useState("");
const value = {
query,
setQuery,
};
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
};
const useAppContext = () => useContext(AppContext);
export { AppContext, AppContextProvider, useAppContext };
pages/_app.jsx
function App({ Component, pageProps }) {
return (
<AppContextProvider>
{Component.getLayout(<Component {...pageProps} />)}
</AppContextProvider>
);
}
component/layout.jsx
const Layout = ({ children }) => {
const { setQuery } = useAppContext();
const onAppBarInputChange = (e) => setQuery(e.target.value);
...(snip)...
todos.jsx
const Todos = () => {
const { query } = useAppContext();
...(snip)...
};
Todos.getLayout = function getLayout(page) {
return <Layout>{page}</Layout>;
};
Related
This question already exists:
update context real time in react [duplicate]
Closed 1 year ago.
I am trying to achieve when user upload their profile image on website and it auto change the old profile picure into new profile picture instead user have to log out and log back in to make it works.
Here is my front end code base:
const UserCard = ({ picture, name, userEmail, isVerified, id, setPicture, setUser}) => {
const [imageSelected, setImageSelected] = useState("");
useEffect(() => {
if (imageSelected !== '') {
uploadImage();
}
}, [imageSelected]);
const uploadImage = () => {
const formData = new FormData();
formData.append("file", imageSelected);
formData.append("id", id);
axios
.post("/api/v1/users/upload/image", formData, {
headers: { "Content-Type": "multipart/form-data" },
})
.then((response) => {
setPicture(response.data.data.imageUrl);
setUser(prev => ({ ...prev, picture: response.data.data.imageUrl }));
});
};
// End of Method
const inputFile = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted file input element
inputFile.current.click();
};
return (
<div className="avatar--icon_profile">
<Card className="profile--card_container">
<CardContent>
{picture ? (
<div>
<input
className="my_file"
type="file"
ref={inputFile}
onChange={(e) => setImageSelected(e.target.files[0])}
/>
<div className="profile-image">
<Avatar
src={picture}
alt="Avatar"
className="avatar--profile_image"
onClick={onButtonClick}
/>
</div>
</div>
</div>
and here is my backend router to send image from client to cloudinary (where I store all the images):
router.post('/upload/image', function (req, res, next) {
const dUri = new Datauri();
const dataUri = (req) => dUri.format(path.extname(req.name).toString(), req.data);
if (req.files !== undefined && req.files !== null) {
const { file, id } = req.files;
const newFile = dataUri(file).content;
cloudinary.uploader.upload(newFile)
.then(result => {
const imageUrl = result.url;
const data = {id : req.body.id, imageUrl };
updateAvatar(data);
return res.status(200).json({ message: 'Success', data: { imageUrl } });
}).catch(err => res.status(400).json({message:'Error', data: { err}}));
} else {
return res.status(400).json({ message: 'Error' });
}
});
How can I achieve it ?
Added GlobalState.js:
const GlobalState = (props) => {
// User State -----------------------------------------------------------------------------
const [currentUser, setUser] = useState(props.serverUserData);
// This method is passed through context to update currentUser
const updateUser = (userData) => {
setUser(userData);
};
// Modal State -----------------------------------------------------------------------------
const [isModalOpen, setModalOpenState] = useState(false);
const [modalToDisplay, setModalToDisplay] = useState("signup");
// This function will be provided to any function that needs to toggle the modal.
const toggleModal = () => {
// Take the previous state and flip it.
setModalOpenState((prevState) => !prevState);
};
// This method is passed through context to update the next modal to open.
const setModal = (name) => {
// Take the passed in modal name and set state.
setModalToDisplay(name);
};
// Loading State ---------------------------------------------------------------------------
// NOT REACT STATE
const [loading, setLoadingState] = useState(false);
// This state will be used as messages in effects to refetch data.
const [reloadThisData, setWhatToReload] = useState("");
// User profile id for query ----------------------------------------------------------------
const [userProfileId, setUserProfileId] = useState("");
// Flag to determine if the header should change css style. ----------------------------------------------------------------
const [adjustBrightness, setAdjustBrightness] = useState(false);
// This is the object passed to GlobalContext.Provider
const providerValues = {
isModalOpen,
toggleModal,
modalToDisplay,
setModal,
currentUser,
updateUser,
loading,
setLoadingState,
reloadThisData,
setWhatToReload,
userProfileId,
setUserProfileId,
adjustBrightness,
setAdjustBrightness,
};
return (
<GlobalContext.Provider value={providerValues}>
{props.children}
</GlobalContext.Provider>
);
};
export default GlobalState;
Added console.log(currentUser):
{id: "a9aa869e-e28b-4a06-b5c7-88571d490e04", name: "nhan nguyen", email: "nhannguyen4119#gmail.com", publicId: "nh1615539696370", picture: "http://res.cloudinary.com/teammateme/image/upload/v1617073225/hvckrm6bklbpjk9njrlf.jpg", …}
email: "nhannguyen4119#gmail.com"
id: "a9aa869e-e28b-4a06-b5c7-88571d490e04"
isSessionValid: true
name: "nhan nguyen"
picture: "http://res.cloudinary.com/teammateme/image/upload/v1617073225/hvckrm6bklbpjk9njrlf.jpg"
publicId: "nh1615539696370"
__proto__: Object
Update my code after edit:
const UserProfile = () => {
const appState = useContext(GlobalContext);
const { currentUser, setUser } = appState;
const { email, name, id } = currentUser;
const [isVerified, setIsVerified] = useState(false);
const [picture, setPicture] = useState(currentUser.picture);
const checkVerificationData = () => {
axios.get("/api/v1/profiles/profile").then((res) => {
const { data } = res;
if (data.verifiedDT) {
setIsVerified(data.verifiedDT);
}
});
};
useEffect(() => {
checkVerificationData();
}, [isVerified]);
const classes = useStyles();
return (
<div className={classes.root}>
<Grid item xs={12}
container
direction="row"
justify="center"
alignItems="center"
spacing={4}>
<Grid item>
<Grid item>
<UserCard
picture={picture}
setPicture={setPicture}
userEmail={email}
name={name}
isVerified={isVerified}
id={id}
setUser={setUser}
/>
<br />
</Grid>
<div>
<Grid item>
<div className="profile--layout_userInfo">
<Grid item>
<UserInfo />
</Grid>
</div>
</Grid>
</div>
</Grid>
<div>
<Grid item>
<div className="profile--layout_ratings_reviews_block">
<UserRatingsDetailed userEmail={email} />
</div>
</Grid>
</div>
</Grid>
</div>
);
};
export async function getServerSideProps(context) {
let serverUserData = {};
if (
context.req.session.passport !== undefined &&
context.req.session.passport.user !== undefined
) {
const userPassportInfo = context.req.session.passport.user;
const { id, name, email, publicId, picture } = userPassportInfo;
const isSessionValid = context.req.isAuthenticated();
serverUserData = {
id,
name,
email,
publicId,
picture,
isSessionValid,
};
}
return { props: { serverUserData } };
}
UserProfile.propTypes = {
serverUserData: PropTypes.object,
};
export default UserProfile;
My suggestion from the comments in answer form is to create a manager similar to your other managers in the global context of your react application.
const GlobalState = (props) => {
// Profile Image -----------------------------------------------------------------------
const [currentProfileImage, setProfileImage] = useState(/*props.serverUserData.profileImage??*/);
...
};
export default GlobalState;
Don't forget to update your providerValues to include these new values.
Then, anywhere you use the profile image URI use the currentProfileImage variable from your context provider.
Lastly, when you upload your image to your server, and receive the new URI in the response use the setProfileImage function from global state to update the profile image in your global state.
const uploadImage = () => {
const formData = new FormData();
formData.append("file", imageSelected);
formData.append("id", id);
axios.post("/api/v1/users/upload/image", formData, {
headers: {"Content-Type": "multipart/form-data"},
}).then((response) => {
GlobalState.setProfileImage(response.data.data.imageUrl);
});
};
*I don't have a complete view of your program, and so this is my best guess of a reasonable way to implement your desired behaviour.
You'll need to update the initial state of the useState directive to reflect where in your props structure the profileImage URI is actually located, and you'll need to update the GlobalState placeholder to reflect however you are actually accessing your provided context.
I'd like to pagination news, or use infinite scroll, but as the site is built then served as static files, I'm not sure the best way to go about this?
Is it possible to do without using apollo or react-query?
I did try react-query but couldn't get it to see my datoCMS endpoint.
(Update: comment below about promise)
import React from 'react'
import { useQuery } from 'react-query'
import gql from 'graphql-tag'
import { GraphQLClient, request } from 'graphql-request'
export const useGQLQuery = (key, query, variables, config = {}) => {
const endpoint = `https://graphql.datocms.com/`
const client = new GraphQLClient(endpoint, {
headers: {
authorization: `Bearer MY_TOKEN_HERE`
}
});
// use await to wait until promise has been fulfilled
const fethData = async () => await request(endpoint, client, query, variables)
return useQuery(key, fethData, config); // useQuery from react-query
};
const GET_ALL_NEWS_CONTENT = gql`
query {
allNews {
slug
id
title
}
}
`;
const AllNews = () => {
// Fetch data from custom hook that uses React-Query
const { data, isLoading, error } = useGQLQuery('allNews', GET_ALL_NEWS_CONTENT)
console.log(data)
if (isLoading) return <div>Loading…</div>
if (error) return <div>Something went wrong…</div>
return (
<>
<div className="generic-page">
<h2>All News</h2>
<div>
{data.allNews.map(news => (
<div key={news.title}>{news.title}</div>
))}
</div>
</div>
</>
)
};
export default AllNews;
With a little help from datoCMS and the react-query community, here is the solution! …
import React, { useState } from 'react'
import { useQuery } from 'react-query'
import gql from 'graphql-tag'
import { GraphQLClient } from 'graphql-request'
export const useGQLQuery = (key, query, variables) => {
const endpoint = `https://graphql.datocms.com/`
const client = new GraphQLClient(endpoint, {
headers: {
authorization: `Bearer MY_TOKEN_HERE`,
}
});
return useQuery(
key, () => client.request(query, variables),
{ keepPreviousData: true }
); // useQuery from react-query
};
const GET_ALL_NEWS_CONTENT = gql`
query GetAllNewsContent ($first: IntType = 10, $skip: IntType = 0) {
allNews(first: $first, skip: $skip, orderBy: _createdAt_DESC) {
id
title
slug
}
}
`;
export default function AllNews({ newscount }) {
const first = 5
let [skip, skipNews] = useState(0)
// Fetch data from custom hook that uses React-Query
const { data, status } = useGQLQuery(
['allNews', skip],
GET_ALL_NEWS_CONTENT,
{ skip, first }
);
const itemCount = Math.ceil(newscount / first);
return (
<div className="generic-page">
<button className="example_b"
onClick={() => skipNews(old => Math.max(old - 5, 0))}
disabled={skip === 0}
>
<span>Previous Page</span>
</button>{' '}
<button className="example_f"
onClick={() => skipNews(old => Math.max(old + 5, 0))}
// Disable the Next Page button when the next skip would be higher than the meta count
disabled={skip + 5 > newscount}
>
<span>Next Page</span>
</button>
<h2>All News</h2>
<p>Page {skip / 5 + 1} of {itemCount}</p>
{status === 'loading' && (
<div>Loading…</div>
)}
{status === 'error' && (
<div>Something went wrong…</div>
)}
{status === 'success' && (
<div>
{data.allNews.map(news => (
<div key={news.title}>{news.title}</div>
))}
</div>
)}
</div>
)
};
I have a dashboard displaying links based on user type when you register you choose your role 1, 2, 3, 4, 5. Everything works fine except when pulling data from the DB and showing it on the front end. On insomnia if I send a command adminId: 1 it returns the correct data. Below is the code where I tie the adminId to the data to display the correct links but nothing happens. If anyone can help it would be great! I am storing the adminId in userData and pulling the links from the backend using axios.
const { userData } = useContext(UserContext);
const history = useHistory();
const [links, setLinks] = useState();
const currAdmin = () => {
currAdmin = userData.user.adminId;
}
useEffect(() => {
if (!userData.user)
history.push("/");
const checkAdmin = async () => {
const adminRes = await axios.get('http://localhost:9000/Links/all', { currAdmin });
setLinks(adminRes.data);
};
checkAdmin();
});
return (
<div className="dashboard">
<Header />
<br />
<br />
<h3> Admin Type: </h3>
<ListGroup className="linklist" >
{links && links.map(e => (
<ListGroup.item key={e.adminId}>
{e.links}
</ListGroup.item>
))}
</ListGroup>
</div>
);
}
For your reference, Please let me know if it will help you.
import React, {useState, useEffect} from 'react';
import axios from 'axios';
function FetchAPIData(props) {
const [myData, setData] = useState({ data:[] });
useEffect(() => {
const fetchData = async () => {
const result = await axios(`http://dummy.restapiexample.com/api/v1/employees`,);
setData(result.myData);
};
fetchData();
}, []);
return (
<div>
<span>{JSON.stringify(myData)}</span>
<ul>
{
myData.data.map(item => {
<li key={item.id}>
{item.employee_name}
</li>
})
}
</ul>
</div>
);
}
export default FetchAPIData;
In the bellow example, how can I pass imageAsUrl.imgUrl from the Child component (ImageUpload) to the parent component (UpdateCard).
CHILD Component
import React, { useState, useEffect } from 'react';
import { storage } from '../firebase';
const ImageUpload = () => {
const allInputs = { imgUrl: '' };
const [imageAsUrl, setImageAsUrl] = useState(allInputs);
const [image, setImage] = useState(null);
const handleChange = (e) => {
if (e.target.files[0]) {
setImage(e.target.files[0]);
}
};
useEffect(() => {
if (image) {
const uploadTask = storage.ref(`images/${image.name}`).put(image);
uploadTask.on(
'state_changed',
(snapshot) => {},
(error) => {
console.log(error);
},
() => {
storage
.ref('images')
.child(image.name)
.getDownloadURL()
.then((fireBaseUrl) => {
setImageAsUrl((prevObject) => ({
...prevObject,
imgUrl: fireBaseUrl,
}));
});
}
);
}
}, [image]);
return (
<>
<label className='custom-file-upload'>
<input type='file' onChange={handleChange} />
</label>
<img src={imageAsUrl.imgUrl} alt='sample' />
</>
);
};
export default ImageUpload;
PARENT Component
import React, { useState } from 'react';
import firebase from '../firebase';
import ImageUpload from './ImageUpload';
const UpdateCard = ({ card }) => {
const [originalText, setOriginalText] = useState(card.originalText);
const [translatedText, setTranslatedText] = useState(card.translatedText);
const onUpdate = () => {
const db = firebase.firestore();
db.collection('FlashCards')
.doc(card.id)
.set({ ...card, originalText, translatedText });
timeOutScroll();
};
return (
<>
<div>
{card.imageURL ? (
<img src={card.imageURL} alt='' className='img' />
) : (
<textarea
className='upload-textarea'
value={originalText}
onChange={(e) => {
setOriginalText(e.target.value);
}}
/>
)}
<ImageUpload />
</div>
<textarea
value={translatedText}
onChange={(e) => {
setTranslatedText(e.target.value);
}}
/>
<button onClick={onUpdate}>Update</button>
</>
);
};
export default UpdateCard;
Inside parent,You can define a callback function as prop ref to be called inside the child.
const ImageUpload = ({getURLtoParent}) =>{ <--------------------
const [imageAsUrl, setImageAsUrl] = useState(allInputs);
useEffect(() => {
uploadTask.on(
..............
...
);
if(imageAsUrl.imgUrl !== '')
getURLtoParent(imageAsUrl.imgUrl) <-----------------------
},[image])
}
const UpdateCart = () => {
const[imgURL,setimgURL] = useState(null)
return (
......
<ImageUpload getURLtoParent={ (url) => setimgURL(url) } /> <----------------
.........
)
}
I'm trying to figure out why my code isn't working but I'm still not understanding why I'm getting this type error.
import React, { Component } from 'react';
import axios from 'axios'
class List extends Component {
state = {
title: '',
description: ''
}
componentDidMount(){
const initialState = {
_id: this.props.list._id,
title: this.props.list.title,
description: this.props.list.description
}
this.setState(initialState)
}
handleChange = (event) => {
const { value, name } = event.target
this.setState({[name]: value})
}
handleDelete = () => {
axios.delete(`/api/lists/${this.state._id}`).then(() => {
this.props.getAllLists()
})
}
handleUpdate = () => {
axios.patch(`/api/lists/${this.state._id}`, this.state).then(() => {
console.log("Updated List")
})
}
render() {
return (
<div>
<input onBlur={this.handleUpdate}
onChange={this.handleChange}
type="text" name="title"
value={this.state.title}
/>
<textarea onBlur={this.handleUpdate}
onChange={this.handleChange}
name="description" value={this.state.description}
/>
<button onClick={this.handleDelete}>X</button>
</div>
)
}
}
export default List
This is the Error msg at this link
Added the other part
import React, { Component } from 'react';
import axios from 'axios'
import List from './List';
class ListPage extends Component {
state = {
user: {},
lists: []
}
componentDidMount() {
this.getAllLists()
}
getAllLists = () => {
// make an api call to get one single user
// On the server URL is '/api/users/:userId'
const userId = this.props.match.params.userId
axios.get(`/api/users/${userId}`).then(res => {
this.setState({
user: res.data,
lists: res.data.lists
})
})
}
handleCreateNewList = () => {
const userId = this.props.match.params.userId
const payload = {
title: 'List Title',
description: 'List Description'
}
axios.post(`/api/users/${userId}/lists`, payload).then(res => {
const newList = res.data
const newStateLists = [...this.state.lists, newList]
this.setState({ lists: newStateLists })
})
}
render() {
return (
<div>
<h1>{this.state.user.username}'s List Page</h1>
onClick={this.handleCreateNewList}
New Idea
{this.state.lists.map(list => (
<List getAllLists={this.getAllLists} key={list._id} list={list}/>
))}
</div>
)
}
}
export default ListPage;
Sorry I can't comment yet. The error is because no 'list' prop is being passed to the component. Are you using the component like this:
<List list={{_id: 'someId', title: 'someTitle', description: 'someDesc'}} />
Also, why are you overwriting the state when the component mounts instead of setting the initial state?
I think you should first check if "this.state.lists" is empty or not, before passing the props.