React Router V4 restore navigation state from url - react-router-v4

Let's say we display a dropdown and on select the user is navigated to a different subpage via react router. How can we restore the dropdown selection on page reload with react router? The projectNumber param is only accessible in the Project component.
In some other projects I solved this problem by writing back the route param to a context or redux store from the child component, but this obviously isn't a nice solution, is it?
export const Projects: React.FC = () => {
const { data, loading, error } = useSomeData()
const history = useHistory()
const onSelectProject = useCallback(
(projectNumber: unknown) => {
history.push(`/project/${projectNumber}`)
},
[history],
)
if (error) return <p>{error.toString()}</p>
return (
<>
<Select loading={loading} onChange={onSelectProject} placeholder="Select a project">
{data?.map((project) => (
<Option key={project.id} value={project.number}>
{project.name}
</Option>
))}
</Select>
<Route path="/project/:projectNumber" render={() => <Project />} />
</>
)
}

You can do it via react-router state. Just see the example:
export const Projects = () => {
const history = useHistory()
const { state } = useLocation()
const onSelectProject = useCallback(
(projectNumber) => {
history.push({
pathname: '/project',
state: {
projectNumber
}
})
},
[history],
)
if (error) return <p>{error.toString()}</p>
return (
<>
<Select loading={loading} onChange={onSelectProject} placeholder="Select a project">
{data?.map((project) => (
<Option key={project.id} value={project.number}>
{project.name}
</Option>
))}
</Select>
{state && state. projectNumber && <Project projectNumber={state.projectNumber} />
</>
)
}
It will work fine for reload page, but not for sharing link to someone other.
But if you are going to share link with preselected dropdown, you can use query parameter (GET parameter).

Related

How to store coming data persistently?

I use react-native.
And this is the function that I want to use.
The most important point here is editCommentMutation.
In order to execute this mutation, it requires two variables which are id and payload.
const onUpdateComment = React.useCallback((commentId) => {
setEdit(true);
console.log(commentId, comments);
editCommentMutation({
variables: {
id: parseInt(commentId),
payload: comments,
},
});
}, []);
I get these two variables correctly.
For id , I get it from component screen. So It comes thru argument.
For payload, I get it on the same screen.
Problem here is when I press button1, it sends commentId(id) data from component screen to this page.
And when I press button2, it sends comments(payload) data on this page.
On this App, I press button1 and write comments then button2 in order.
So Each data comes not together, but one by one.
So I execute console.log(commentId, comments),
press button1 : 386 undefined
press button2 : undefined Object { "comments": "뭐야", }
It has undefined for sure..
But in order to execute mutation, I need two data together.
For this, I need to save coming data in somewhere, I guess.
I also tried
const [data, setData] = useState("").
After I save coming commentId to here as:
setData(commentId).
But it isn't saved, just disappears. :)
Can you help me?
Here is a minimal verifiable example. Run the code below and
click ✏️ to edit one of the comments
click ✅ to save your edit and view the updated comment
function App() {
const [edit, setEdit] = React.useState(null)
const [comments, setComments] = React.useState([ "hello", "안녕하세요" ])
const onEdit = editable => event => { setEdit(editable) }
const onUpdate = newComment => event => {
/* execute your graphql mutation here */
setComments([
...comments.slice(0, edit.index),
newComment,
...comments.slice(edit.index + 1)
])
setEdit(null)
}
return <div>
{comments.map((comment, index) =>
<p>{comment}
<button
children="✏️"
onClick={onEdit({comment, index})}
disabled={Boolean(edit)}
/>
</p>
)}
{edit ? <EditComment text={edit.comment} onUpdate={onUpdate} /> : null}
</div>
}
function EditComment({ text, onUpdate }) {
const [value, setValue] = React.useState(text)
return <div>
<input value={value} onChange={e => setValue(e.target.value)} />
<button onClick={onUpdate(value)} children="✅" />
</div>
}
ReactDOM.render(<App/>, document.querySelector("#app"))
button[disabled] { opacity: 0.5; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
In your App you will execute your mutation in onUpdate. A cancel button ❌ can easily be defined using const onCancel = event => { setEdit(null) } and pass it to EditComment.

Adding custom component with an onClick icon

I'm currently using react-select and I want to create a custom Option component that has an icon that allows interaction (e.g. adding to favourites). I've tried doing
const Option = props => {
return <components.Options {...props}>
<p>Text</p>
<IonIcon onClick={console.log("clicked")} />
</components.Options>
}
Doing this does not allow the IonIcon to be clicked and the onClick behaves like the default Option.
I have also tried:
<div>
<p>Text</p>
<IonIcon onClick={console.log("clicked")} />
</div>
as the return. While doing this, the default Option behaviour is removed, it does not allow the IonIcon to be clicked either.
For both methods I've tried, opening the menu will trigger the onClick, i.e. print "clicked", and clicking the IonIcon doesn't trigger anything.
I've set the zIndex of the icon to 10000 as well.
Thanks in advance! It'll be really cool to include the ability to favourite in select!
Before Clarifications
If I didn't understand wrong you want your new Component to have some kind of text (maybe passed by prop?) and the Icon clickable, after the click the Icon should change color.
For the Icon I'm using react-icons .
App.js
import React,{useState} from 'react';
import { IoStar } from "react-icons/io5";
function App() {
return (
<div style={{textAlign:"center"}}>
<ul style={{listStyleType:"none"}}>
<Option value="Location 1"/>
<Option value="Location 2"/>
<Option value="Location 3"/>
<Option value="Location 4"/>
<Option value="Location 5"/>
</ul>
</div>
);
}
const Option = (props) =>
{
let {value} = props; //Value taken from props
const defaultStyle = //Default Logo Style -> Cursor: Pointer is used to make the cursor become a "hand" when "hover" on the icon
{
cursor:"pointer",
fontSize:"40px"
}
const starPressed = () => //Function triggered on the "On Click"
{
if(isYellow)
setIconStyle(defaultStyle); //Color Default
else
setIconStyle({...iconStyle,color : "#f5d442"}); //Color Change
setIsYellow(!isYellow);
}
const [iconStyle,setIconStyle] = useState(defaultStyle); //UseState
const [isYellow,setIsYellow] = useState(false); //UseState
//return
return(
<li>
<span style={{fontSize:"40px"}}>{value}</span> <span><IoStar onClick={starPressed} style={iconStyle}/></span>
</li>
)
}
export default App;
Basically I'm using useState to re-render after the : iconStyle changes.
I believe it is what you were searching for.
EDIT After Clarifications
This is a BIG BIG work-around, hope it would help you.
I'm sure there are better ways to pass data and other things, and I'm still looking for them, but this is my first approach to it.
(Never used react-select).
Hope it helps.
import React,{useState} from 'react';
import { IoStar } from "react-icons/io5";
import Select from "react-select";
function App() {
const [isYellow,setIsYellow] = useState(
[
false,
false
]
); //UseState
const options =
[
{
value:0,
label:"Option 1",
isYellow: isYellow,
setIsYellow: setIsYellow
},
{
value:1,
label:"Option 2",
isYellow: isYellow,
setIsYellow: setIsYellow
}
];
return (
<>
<Select closeMenuOnSelect={false} options={options} formatOptionLabel={FormatOptionLabel}/>
</>
);
}
const FormatOptionLabel = ({ value, label,isYellow,setIsYellow}) =>
{
const style1 =
{
zIndex:"100",
cursor:"pointer",
fontSize:"30px"
}
const style2 =
{
zIndex:"100",
cursor:"pointer",
fontSize:"30px",
color : "#f5d442"
}
//return
return(
<>
<span style={{fontSize:"20px"}} value={value}>{label}</span> <span style={{float:"right"}}><IoStar onClick={(event) => { let array = isYellow; array[value]=!array[value]; setIsYellow(array);}} style={isYellow[value] ? style2 : style1}/></span>
</>
)
}
export default App;

how do I pass ref to state?

I created a ref and passed it to the component as an argument. Now how can I write it to State without dispatch?
const myRef = createRef<any>();
const KeyboardAvoidingWrapper: React.FC<IKeyboardAvoidingProps> = (
props: IKeyboardAvoidingProps,
=> {
if (isAndroid) {
return (
<ScrollView ref={myRef} style={styles.scroll} contentContainerStyle={styles.scrollContent}>
<KeyboardAvoiding>{props.children}</KeyboardAvoiding>
</ScrollView>
);
}
This example might help you.
<Component ref={(ref) => {
// You can now write ref to state,
// which I will not recommend
// or pass it to callback
props.getInnerRef(ref);
}} />
// You need to pass getInnerRef
<Component getInnerRef={(ref) => {
this.innerRef = ref;
}}
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
Please refer the react documentation. It clearly defines how to use refs.
ForwadingRefs

Creating new record does not register in list without refresh and creates duplicate record

Below is a screenshot of the Redux dev tools. This is a list of records of a particular resource. As you can see, the newly added record has a duplicate with the key undefined. It also does not register on the list without a refresh.
Redux Dev Tools
Here is the create component:
export const OrgCreate = (props: ReactAdminComponentProps) => {
const controller = useCreateController(props);
return (
<Create {...props} {...controller}>
<SimpleForm>
<TextInput source="name" />
</SimpleForm>
</Create>
);
};
Here is the list component:
export const OrgList = (props: ReactAdminComponentProps) => {
const controller = useListController(props);
return (
<List {...props} {...controller} exporter={false} bulkActionButtons={false}>
<Datagrid rowClick="edit" hasBulkActions={false}>
<TextField source="name.value" label="Name" />
<DateField source="createdAt" />
</Datagrid>
</List>
);
};
Here is the create response in the GraphQL provider:
createResponse = (data: any) => data.createOrganization.organization;
Try removing the lines:
const controller = useCreateController(props);
const controller = useListController(props);
and further {...controller}, it should work.

Server-side rendering(express) with react router does not work

I am trying server-side rendering using node js, React, Redux and React-Router.
I followed react-router server-side tutorial but I am only getting root route, no matter what route I put. As you see in the routes.js, I have a route to detail.
I tried path="detail" as well
and Link is like below
<Link to="/detail"> Detail</Link>
When I click that link, it does not even give me error like "no matched route to /detail".
If you want to see all codes - git repo
Here is my code
routes.js
export default ([
<Route path="/" component={App}>
<Route path="/detail" component={DetailView}/>
</Route>
]);
server.js
app.get('/*',(req,res)=>{
match({ routes:routes, location: req.url }, (error, redirectLocation, renderProps) => {
if (error) {
res.status(500).send(error.message)
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search)
} else if (renderProps) {
const store = createStore(Reducers);
const html = renderToString(
<Provider store={store}>
<RouterContext {...renderProps} />
</Provider>
);
res.status(200).send(renderFullPage(html, store));
} else {
res.status(404).send('Not found')
}
})
});
renderFullPage - this basically injects html from rederToString to html string
<div id="app">${html}</div>
<script>
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState)}
</script>
client.js(index.js)
const history =createBrowserHistory();
match({ history, routes }, (error, redirectLocation, renderProps) => {
ReactDom.render(
<Provider store={store} >
<Router {...renderProps} />
</Provider> , document.getElementById('app'))
});
App.js
class App extends React.Component {
render() {
return (
<div>
<div className="container">
<PokemonContainer />
</div>
</div >
)
};
}
Thank you for look into it and please give me any opinion about this.
Try removing the leading slash:
export default ([
<Route path="/" component={App}>
<Route path="detail" component={DetailView}/>
</Route>
]);
No sure if that's the issue with routing but you have to pass store state to renderFullPage not the store itself. So it should be something like this:
} else if (renderProps) {
const store = createStore(Reducers);
const initialState = store.getState()
const html = renderToString(
<Provider store={store}>
<RouterContext {...renderProps} />
</Provider>
);
res.status(200).send(renderFullPage(html, initialState));
}