render my parent component from a call in its child component. But I don't know how to write it correctly.
The context is: When the page is loaded, the code search the userId in localStorage to download the data of a user to display its information.
A link exists in child component to removed the idUser and got an empty form. The problem is that my parent don't re-render automatically.
<Link to="/consultation" onClick={() => {localStorage.removeItem('idUser'); this.props.callBack();}}>here.</Link>
-
else if(user){contents = [<Message_UserIdentified user callBack={this.forceUpdate()}/>, contentform];}
I tried something, but it's not work. Could you help me please ?
I don't know how to parse it (the item "user" and in same time the callBack).
The code write me that it don't find the function forceUpdate().
This is my parents page called "Consultation" (I removed some parts of code to be clearer):
import { Message_EmptyUserID, Message_NeedToBeConnected, Message_UserIdentified } from '../component/message_consultation';
const Consultation = () => {
const [user, setUser] = useState(null);
const [login] = useState(jwtUtils.checkToken());
const [userId, setUserId] = useState(false);
function handleChange(event) {
setUser({
...user,
[event.target.name]: event.target.value
})
};
useEffect(() => {
if (login === false || localStorage.getItem("idUser") === null) return;
axios.get(`http://localhost:5000/users/${localStorage.getItem("idUser")}`, {
headers: {
'token': localStorage.getItem('token')
}
})
.then(res => {
setUser(res.data);
if(res.data !== undefined){
setUserId(true);
}
})
.catch(error => console.log(error))
}, []);
let contents, contentform;
contentform = (
<div >...
</div>
);
if (login === false) contents = Message_NeedToBeConnected();
else if (userId === false) contents = contentform;
else if(user){contents = [<Message_UserIdentified user callBack={this.forceUpdate()}/>, contentform];}
return (
<div className="case card border-secondary mb-3" styles="max-width: 20rem;">
<div className="card-header">Consultation</div>
<div className="card-body">
{contents}
</div>
</div>
);
}
export default Consultation;
This is my child component called "Message_UserIndentified":
const Message_UserIdentified = (user) => {
return(
<Alert color="primary" className="alert alert-dismissible alert-info">
<h4>{user === null || user === undefined ? "" : user.firstname} {user === null || user === undefined ? "" : user.lastname}</h4>
If you are not {user === null || user === undefined ? "" : user.firstname} and you are <mark>already registered</mark>, find your consultation <Link to="/register" onClick={localStorage.removeItem('idUser')}>here.</Link> <hr/>
If you are not {user === null || user === undefined ? "" : user.firstname} and your are <mark>not registered</mark>, add your consultation <Link to="/consultation" onClick={() => {localStorage.removeItem('idUser'); this.props.callBack();}}>here.</Link>
</Alert>
);
}
First of all, you mix up the hooks API with the class API. You need to choose one, React doesn't allow to use both in a single component.
React functional components don't have this and this.forceUpdate. See the official note on how to force update in a functional component with hooks:
const Consultation = () => {
const [ignored, forceUpdate] = React.useReducer(x => x + 1, 0);
// ...
return <Message_UserIdentified user callBack={forceUpdate} />;
};
Second, you mustn't call the function when you want to pass it as a callback. When you run callBack="forceUpdate()", the function is executed once and the returned value is passed to the child prop. You need to run callBack="forceUpdate" to pass the function itself so that the child component can execute it whenever it wants.
This rule is applicable to class components too, but you should also bind the forceUpdate method to the current component:
<button onClick={() => this.forceUpdate()}>Demo</button>
// or
<button onClick={this.forceUpdate.bind(this)}>Demo</button>
From the architecture point of view, you shouldn't use localStorage to share data between components of the application because localStorage isn't reactive (it doesn't notify when a content changes) so you'll struggle updating all the components manually. You'd rather read'n'write localStorage only in Consultation or use a reactive state storage like Redux and MobX (in more complex applications). Local storage may be used at the same time: read on an application start and write when something changes.
Related
I don't know how to form my question, but I'll try explain my problem. So I'm creating local meet app similar as (tinder). But I spent about 2 hours and can't figure out how to create a logic, that when I get all users from database I display all users at main page, but there is a problem, it also display the user which is already logged in and he can see himself at main page. Is there any solution to display all users except that user which is logged in? I was trying to use filter method, but can't figure it out how to create correct logic or maybe there's a simple method how to do it?
There I'm getting all users and display them:
export default function HomePage() {
const [allUsers, getAllUsers] = useState([])
useEffect(() => {
async function fetchUsers() {
const resp = await get('api')
getAllUsers(resp)
console.log(resp)
}
fetchUsers()
}, [])
return (
<div className='home__page'>
<Toolbar />
<div className="home__users">
{allUsers && allUsers.map((users, i) => <SingleUser users={users} key={i} />)}
</div>
<footer className='footer'>
<p>All rights reserved by Cartoon Match organization. To read more about our policy <Link to='/policy'>click here</Link>. </p>
</footer>
</div>
)
}
There's SingleUser component:
import React from "react";
export default function SingleUser({ users }) {
return (
<div className='single__user'>
<img src={users.image[0]} alt="" />
<h3>{users.firstName} {users.lastName}</h3>
<h4>{users.gender}</h4>
</div>
);
}
I'm also have a users secret saved in localstorage, maybe it could be easier to think simple solution
Normally you'll have access to the current loged in user stored in a global context, after user login. And there're two ways to filter the item you don't want to render in a list:
1. Conditional rendering (using && the Logical AND operator) inside the map() method:
export default function HomePage() {
const [currentUser, setCurrentUser] == useState(); // <- stores current loged in user
const [allUsers, getAllUsers] = useState([])
return (
<div className="home__users">
{allUsers && allUsers.map((user, i) => user.id !== currentUser.id && (<SingleUser users={user} key={user.id} />))}
</div>
)
}
2. Filter the list first and then render it, by chainning array method list.filter().map():
export default function HomePage() {
const [currentUser, setCurrentUser] == useState(); // <- stores current loged in user
const [allUsers, getAllUsers] = useState([])
return (
<div className="home__users">
{allUsers && allUsers.filter(user => user.id !== currentUser.id).map((user, i) => <SingleUser users={user} key={user.id} />)}
</div>
)
}
Array method accept an callback function, that we can spcify some condition, and check if every iteration of the item in the array meet such condition, if it meets the condition, then return the item, and filter() as a whole returns a shallow copy of the array itself, so we can continue to chain the method with map():
allUsers.filter(user => user.id !== currentUser.id).map(user => {...})
This means, if the user with id not equal to the loged in user, then return the user item.
See more at MDN docs: Logical AND (&&), filter method
I am calling components as folloews
{userAddresses.map((useraddress, index) => {
return (
<div key={index}>
<Address useraddress={useraddress} />
</div>
);
})}
Their state:
const [showEditAddress, setShowEditAddress] = useState(false);
and this is how I am handling their states
const switchEditAddress = () => {
if (showEditAddress === false) {
setShowEditAddress(true);
} else {
setShowEditAddress(false);
}
};
Well, it's better if you want to toggle between true and false to use the state inside useEffect hook in react.
useEffect will render the component every time and will get into your condition to set the state true or false.
In your case, you can try the following:
useEffect(() => { if (showEditAddress === false) {
setShowEditAddress(true);
} else {
setShowEditAddress(false);
} }, [showEditAddress])
By using useEffect you will be able to reset the boolean as your condition.
Also find the link below to react more about useEffect.
https://reactjs.org/docs/hooks-effect.html
It would be best in my opinion to keep your point of truth in the parent component and you need to figure out what the point of truth should be. If you only want one component to be editing at a time then I would just identify the address you want to edit in the parent component and go from there. It would be best if you gave each address a unique id but you can use the index as well. You could do something like the following:
UserAddress Component
const UserAddress = ({index, editIndex, setEditIndex, userAddress}) => {
return(
<div>
{userAddress}
<button onClick={() => setEditIndex(index)}>Edit</button>
{editIndex === index && <div style={{color: 'green'}}>Your editing {userAddress}</div>}
</div>
)
}
Parent Component
const UserAddresses = () => {
const addresses = ['120 n 10th st', '650 s 41 st', '4456 Birch ave']
const [editIndex, setEditIndex] = useState(null)
return userAddresses.map((userAddress, index) => <UserAddress key={index} index={index} editIndex={editIndex} setEditIndex={setEditIndex} userAddress={userAddress}/>;
}
Since you didn't post the actual components I can only give you example components but this should give you an idea of how to achieve what you want.
PDFDownloadLink from react-pdf library downloads a pdf when someone clicks on it.
I want to trigger this click event via code based on some condition .
How do I explicitly invoke the click of PDFDownloadLink through code?
A bit late, but you can pass a ref to the render function's return value and use it to call click() on imperatively. For that to work you need to use a separate component wrapper:
const DownloadLink = memo(function () {
const linkRef = useRef(null)
const onLoadingFinished = useCallback(function () {
// When this function is called the first time it is safe to initiate the download
const elem = linkRef?.current
if (elem !== null) {
elem.click()
}
}, [])
return (
<PDFDownloadLink document={<MyDoc />} fileName={'my-file.pdf'}>
{({ blob, url, loading, error }) => (
// You shouldn't call setState() here, so we need to use a separate component to keep track of whether the document has finished rendering
<WorkaroundContainer ref={linkRef} loading={loading} onLoadingFinished={onLoadingFinished} />
)}
</PDFDownloadLink>
)
})
const WorkaroundContainer = forwardRef(function ({ loading, onLoadingFinished }, ref) {
useEffect(() => {
if (!loading) {
onLoadingFinished()
}
}, [loading])
// If you only want to initiate the download imperatively, hide the element via CSS (e.g. `visibility: hidden`)
return (
<div ref={ref}>
{loading ? 'Loading...' : 'Download PDF'}
</div>
)
})
I have a parent component with an if statement to show 2 different types of buttons.
What I do, on page load, I check if the API returns an array called lectures as empty or with any values:
lectures.length > 0 ? show button A : show button B
This is the component, called main.js, where the if statement is:
lectures.length > 0
? <div onClick={() => handleCollapseClick()}>
<SectionCollapse open={open} />
</div>
: <LectureAdd dataSection={dataSection} />
The component LectureAdd displays a + sign, which will open a modal to create a new Lecture's title, while, SectionCollapse will show an arrow to show/hide a list of items.
The logic is simple:
1. On page load, if the lectures.lenght > 0 is false, we show the + sign to add a new lecture
OR
2. If the lectures.lenght > 0 is true, we change and show the collpase arrow.
Now, my issue happens when I add the new lecture from the child component LectureAdd.js
import React from 'react';
import { Form, Field } from 'react-final-form';
// Constants
import { URLS } from '../../../../constants';
// Helpers & Utils
import api from '../../../../helpers/API';
// Material UI Icons
import AddBoxIcon from '#material-ui/icons/AddBox';
export default ({ s }) => {
const [open, setOpen] = React.useState(false);
const [ lucturesData, setLecturesData ] = React.useState(0);
const { t } = useTranslation();
const handleAddLecture = ({ lecture_title }) => {
const data = {
"lecture": {
"title": lecture_title
}
}
return api
.post(URLS.NEW_COURSE_LECTURE(s.id), data)
.then(data => {
if(data.status === 201) {
setLecturesData(lucturesData + 1) <=== this doesn't trigger the parent and the button remains a `+` symbol, instead of changing because now `lectures.length` is 1
}
})
.catch(response => {
console.log(response)
});
}
return (
<>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
<AddBoxIcon />
</Button>
<Form
onSubmit={event => handleAddLecture(event)}
>
{
({
handleSubmit
}) => (
<form onSubmit={handleSubmit}>
<Field
name='lecture_title'
>
{({ input, meta }) => (
<div className={meta.active ? 'active' : ''}>
<input {...input}
type='text'
className="signup-field-input"
/>
</div>
)}
</Field>
<Button
variant="contained"
color="primary"
type="submit"
>
ADD LECTURE
</Button>
</form>
)}
</Form>
</>
)
}
I've been trying to use UseEffect to trigger a re-render on the update of the variable called lucturesData, but it doesn't re-render the parent component.
Any idea?
Thanks Joe
Common problem in React. Sending data top-down is easy, we just pass props. Passing information back up from children components, not as easy. Couple of solutions.
Use a callback (Observer pattern)
Parent passes a prop to the child that is a function. Child invokes the function when something meaningful happens. Parent can then do something when the function gets called like force a re-render.
function Parent(props) {
const [lectures, setLectures] = useState([]);
const handleLectureCreated = useCallback((lecture) => {
// Force a re-render by calling setState
setLectures([...lectures, lecture]);
}, []);
return (
<Child onLectureCreated={handleLectureCreated} />
)
}
function Child({ onLectureCreated }) {
const handleClick = useCallback(() => {
// Call API
let lecture = callApi();
// Notify parent of event
onLectureCreated(lecture);
}, [onLectureCreated]);
return (
<button onClick={handleClick}>Create Lecture</button>
)
}
Similar to solution #1, except for Parent handles API call. The benefit of this, is the Child component becomes more reusable since its "dumbed down".
function Parent(props) {
const [lectures, setLectures] = useState([]);
const handleLectureCreated = useCallback((data) => {
// Call API
let lecture = callApi(data);
// Force a re-render by calling setState
setLectures([...lectures, lecture]);
}, []);
return (
<Child onLectureCreated={handleLectureCreated} />
)
}
function Child({ onLectureCreated }) {
const handleClick = useCallback(() => {
// Create lecture data to send to callback
let lecture = {
formData1: '',
formData2: ''
}
// Notify parent of event
onCreateLecture(lecture);
}, [onCreateLecture]);
return (
<button onClick={handleClick}>Create Lecture</button>
)
}
Use a central state management tool like Redux. This solution allows any component to "listen in" on changes to data, like new Lectures. I won't provide an example here because it's quite in depth.
Essentially all of these solutions involve the same solution executed slightly differently. The first, uses a smart child that notifies its parent of events once their complete. The second, uses dumb children to gather data and notify the parent to take action on said data. The third, uses a centralized state management system.
The Mutation component in react-apollo exposes a handy loading boolean in the render prop function which is ideal for adding loaders to the UI whilst a request is being made. In the example below my Button component calls the createPlan function when clicked which initiates a GraphQL mutation. Whilst this is happening a spinner appears on the button courtesy of the loading prop.
<Mutation mutation={CREATE_PLAN}>
{(createPlan, { loading }) => (
<Button
onClick={() => createPlan({ variables: { input: {} } })}
loading={loading}
>
Save
</Button>
)}
</Mutation>
The issue I have is that other aspects of my UI also need to change based on this loading boolean. I have tried lifting the Mutation component up the React tree so that I can manually pass the loading prop down to any components which rely on it, which works, but the page I am building has multiple mutations that can take place at any given time (such as deleting a plan, adding a single item in a plan, deleting a single item in a plan etc.) and having all of these Mutation components sitting at the page-level component feels very messy.
Is there a way that I can access the loading property outside of this Mutation component? If not, what is the best way to handle this problem? I have read that you can manually update the Apollo local state using the update function on the Mutation component (see example below) but I haven't been able to work out how to access the loading value here (plus it feels like accessing the loading property of a specific mutation without having to manually write it to the cache yourself would be a common request).
<Mutation
mutation={CREATE_PLAN}
update={cache => {
cache.writeData({
data: {
createPlanLoading: `I DON"T HAVE ACCESS TO THE LOADING BOOLEAN HERE`,
},
});
}}
>
{(createPlan, { loading }) => (
<Button
onClick={() => createPlan({ variables: { input: {} } })}
loading={loading}
>
Save
</Button>
)}
</Mutation>
I face the same problem in my projects and yes, putting all mutations components at the page-level component is very messy. The best way I found to handle this is by creating React states. For instance:
const [createPlanLoading, setCreatePLanLoading] = React.useState(false);
...
<Mutation mutation={CREATE_PLAN} onCompleted={() => setCreatePLanLoading(false)}>
{(createPlan, { loading }) => (
<Button
onClick={() => {
createPlan({ variables: { input: {} } });
setCreatePLanLoading(true);
}
loading={loading}
>
Save
</Button>
)}
</Mutation>
I like the answer with React States. However, when there are many different children it looks messy with so many variables.
I've made a bit update for it for these cases:
const Parent = () => {
const [loadingChilds, setLoading] = useState({});
// check if at least one child item is loading, then show spinner
const loading = Object.values(loadingChilds).reduce((t, value) => t || value, false);
return (
<div>
{loading ? (
<CircularProgress />
) : null}
<Child1 setLoading={setLoading}/>
<Child2 setLoading={setLoading}/>
</div>
);
};
const Child1 = ({ setLoading }) => {
const [send, { loading }] = useMutation(MUTATION_NAME);
useEffect(() => {
// add info about state to the state object if it's changed
setLoading((prev) => (prev.Child1 !== loading ? { ...prev, Child1: loading } : prev));
});
const someActionHandler = (variables) => {
send({ variables});
};
return (
<div>
Child 1 Content
</div>
);
};
const Child2 = ({ setLoading }) => {
const [send, { loading }] = useMutation(MUTATION_NAME2);
useEffect(() => {
// add info about state to the state object if it's changed
setLoading((prev) => (prev.Child2 !== loading ? { ...prev, Child2: loading } : prev));
});
const someActionHandler = (variables) => {
send({ variables});
};
return (
<div>
Child 2 Content
</div>
);
};